import logging, sys

import sqlalchemy as sa
from sqlalchemy import orm 
from zope.sqlalchemy import ZopeTransactionExtension

from clutch.util import bag
from clutch.globals import G, C
from clutch.lazy import ContextualLazyEnviron
from clutch.proxy import object_manager

engine = session = None

log = logging.getLogger('clutch.database')

def app_id():
    return id(G.application)

def dump_verbose(obj):
    yield '<%s>' % obj.__class__.__name__
    for k, v in obj.__json__().iteritems():
        if isinstance(v, list):
            yield '    %s: [' % k
            for vv in v:
                for line in dump_verbose(vv):
                    yield '        ' + line
            yield '    ]'
        elif hasattr(v, 'dump_verbose'):
            yield '    %s:' % k
            for line in v.dump_verbose(join=False):
                yield '        %s' % line
        else:
            yield '    %s:%s' % (k,v)
            
def make_environ():
    environ = ContextualLazyEnviron(lambda:id(G.application))
    engine = environ.new(lambda:sa.create_engine(G.config.clutch.db.uri))

    class BaseClass(object):

        __metaclass__ = environ.LazyMeta

        extra_json_properties = []

        def __init__(self, **kw):
            for k,v in kw.iteritems():
                setattr(self, k, v)

        @property
        def _props(self):
            m = orm.object_mapper(self)
            for prop in m.iterate_properties:
                if isinstance(prop, orm.ColumnProperty):
                    yield prop.key, getattr(self, prop.key)
                elif prop.key in self.extra_json_properties:
                    yield prop.key, getattr(self, prop.key)

        @property
        def label(self):
            return str(self)

        def __json__(self):
            result = dict((k,v) for k,v in self._props)
            return result

        def __repr__(self):
            props = ' '.join('%s=%s' % (k,v)
                             for k,v in self.__json__().iteritems())
            return '<%s %s>' % (self.__class__.__name__, props)

        def dump_verbose(self, join=True):
            if join:
                return '\n'.join(dump_verbose(self))
            else:
                return dump_verbose(self)

    result = bag(
        environ = environ,
        engine = engine,
        metadata = environ.new(lambda:sa.MetaData(bind=engine)),
        session = environ.new(make_session),
        metaclass = environ.LazyMeta,
        AppTable = environ.new(lambda:Table),
        AppColumn = environ.new(lambda:sa.Column),
        AppForeignKey = environ.new(lambda:ForeignKey),
        BaseClass = BaseClass)
    return result

class Table(sa.Table):

    def __init__(self, name, metadata, *args, **kwargs):
        schema = G.config.clutch.db.schema
        table_prefix = G.config.clutch.db.table_prefix
        if table_prefix: name = table_prefix + name
        if schema and 'schema' not in kwargs:
            kwargs['schema'] = schema
        new_args = []
        for a in args:
            if hasattr(a, 'sa_columns'):
                new_args += list(a.sa_columns())
            else:
                new_args.append(a)
        sa.Table.__init__(self, name, metadata, *new_args, **kwargs)

    def __call__(self):
        return self

class ForeignKey(sa.ForeignKey):

    def __init__(self, column, **kwargs):
        column = app_local_column(column)
##        if isinstance(column, basestring):
##            schema = G.config.clutch.db.schema
##            table_prefix = G.config.clutch.db.table_prefix
##            parts = column.split('.')
##            if len(parts) == 3:
##                schema = parts[0]
##                parts = parts[1:]
##            if schema:
##                column = '%s.%s%s.%s' % (
##                    schema, table_prefix, parts[0], parts[1])
##            else:
##                column = '%s%s.%s' % (
##                    table_prefix, parts[0], parts[1])
        sa.ForeignKey.__init__(self, column, **kwargs)

    def __call__(self):
        return self

class ForeignKeyConstraint(sa.ForeignKeyConstraint):

    def __init__(self, columns, refcolumns, **kwargs):
        columns = map(app_local_column, columns)
        refcolumns = map(app_local_column, refcolumns)
        sa.ForeignKeyConstraint.__init__(self, columns, refcolumns, **kwargs)

    def __call__(self):
        return self

def app_local_column(column):
    if isinstance(column, basestring):
        schema = G.config.clutch.db.schema
        table_prefix = G.config.clutch.db.table_prefix
        parts = column.split('.')
        if len(parts) == 3:
            schema = parts[0]
            parts = parts[1:]
        if schema:
            column = '%s.%s%s.%s' % (
                schema, table_prefix, parts[0], parts[1])
        else:
            column = '%s%s.%s' % (
                table_prefix, parts[0], parts[1])
    return column

def make_session():
    kwargs = dict(
        bind=engine, autoflush=True, autocommit=True)
    if G.application.in_wsgi_server:
        kwargs.update(extension=ZopeTransactionExtension())
    session_factory = orm.sessionmaker(**kwargs)
    session = orm.scoped_session(session_factory)
    old_mapper = session.mapper
    def mapper(class_, tbl, *args, **kwargs):
        om = object_manager(class_)
        class_ = om.obj
        if 'inherits' in kwargs:
            om = object_manager(kwargs['inherits'])
            kwargs['inherits'] = om.obj
        return old_mapper(class_, tbl, *args, **kwargs)
    session.mapper = mapper
    return session


